/*************************************************
* Title：TinyToolKit
* Author：Opportunity 
* Describe：TCP服务器端主要逻辑
* CreateTime：2020-08-13 11:54:51
* Version：1.0
* Modify Recorder：
*************************************************/

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using Object = System.Object;

namespace TinyToolKit.Network
{
    public class TcpServerHelper
    {
        private long _heartBeatTime = 40;
        private TcpListener _tcpListener;
        private Conn[] _conns; //所有客户端的连接
        private ProtocolBytes _protocol;
        private Dictionary<RequestType, HandleMsgBase> _requestDic;
        private System.Timers.Timer _timer;

        private Action<IPEndPoint> _connectClientCb; //网络连接成功委托事件
        private Action<IPEndPoint> _disconnectClientCb; //网络异常断开委托事件

        #region Public Func 

        public int ConnUsedCount
        {
            get
            {
                if (_conns == null) return 0;
                int count = 0;
                for (int i = 0; i < _conns.Length; i++)
                {
                    if (_conns[i] == null || !_conns[i].isUse) continue;
                    count++;
                }

                return count;
            }
        }

        public TcpServerHelper()
        {
            _protocol = new ProtocolBytes();

            HandleConnMsg handleConnMsg = new HandleConnMsg(this);
            _requestDic = new Dictionary<RequestType, HandleMsgBase>
            {
                {RequestType.Conn, handleConnMsg}
            };
        }

        /// <summary>
        /// 开启服务器
        /// </summary>
        /// <param name="ip">服务器的IP</param>
        /// <param name="port">端口号</param>
        /// <param name="maxConn">客户端连接最大数</param>
        /// <param name="connectClientCb">客户端连接时的回调函数</param>
        /// <param name="disconnectClientCb">客户端断开连接时的回调函数</param>
        public bool Start(string ip, int port, int maxConn,Action<IPEndPoint> connectClientCb = null,Action<IPEndPoint> disconnectClientCb = null)
        {
            try
            {
                if (maxConn <= 0) maxConn = 1;
                _conns = new Conn[maxConn];

                IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
                _tcpListener = new TcpListener(ipEndPoint);
                _tcpListener.Start(); //开始监听客户端的连接
                _tcpListener.BeginAcceptSocket(OnServerConnect, _tcpListener); //异步接收Tcp连接
                TLogTool.Info($"【TcpServerNet】服务端({ipEndPoint})开始监听客户端连接");
            
                _connectClientCb = connectClientCb;
                _disconnectClientCb = disconnectClientCb;

                //定时器，线程池
                _timer = new System.Timers.Timer(1000) {AutoReset = false, Enabled = true};
                _timer.Elapsed += HandleMainTimer;

                return true;
            }
            catch (Exception ex)
            {
                TLogTool.Info("【TcpServerNet】服务器开启时发生错误：" + ex);
                return false;
            }
        }

        /// <summary>
        /// 添加处理消息的方法类
        /// </summary>
        /// <param name="requestType">处理消息的类型</param>
        /// <param name="handleMsg">方法类</param>
        public void AddHandleMsgMethod(RequestType requestType, HandleMsgBase handleMsg)
        {
            if (_requestDic.ContainsKey(requestType) == false)
                _requestDic.Add(requestType, handleMsg);
        }

        /// <summary>
        /// 消息广播，将消息发送给所有有用的客户端连接
        /// </summary>
        /// <param name="protocol">需要广播的协议</param>
        public void Broadcast(ProtocolBase protocol)
        {
            for (int i = 0; i < _conns.Length; i++)
            {
                if (_conns[i] == null || _conns[i].isUse == false) continue;
                TLogTool.Info($"【TcpServerNet】服务端将{protocol.GetRequest()}请求下的{protocol.GetName()}协议发送给：{_conns[i].GetAddress()}");
                _conns[i].Send(protocol.Encode());
            }
        }

        /// <summary>
        /// 关闭服务端程序
        /// </summary>
        public void Close()
        {
            if (_tcpListener == null) return;

            lock (_tcpListener)
            {
                for (int i = 0; i < _conns.Length; i++)
                {
                    Conn conn = _conns[i];
                    if (conn == null || conn.isUse == false) continue;
                    lock (conn)
                    {
                        conn.Close();
                    }
                }

                _tcpListener.Server.Close();
                _tcpListener.Stop();
                _tcpListener = null;
                _timer.Stop();
                _timer.Close();

                TLogTool.Info("【TcpServerNet】服务器关闭");
            }
        }

        #endregion

        #region Internal Func

        //连接客户端的回调
        private void OnServerConnect(IAsyncResult ar)
        {
            TcpListener tcpListener = (TcpListener) ar.AsyncState;
            TcpClient tcpClient = tcpListener.EndAcceptTcpClient(ar);

            try
            {
                int index = GetNewConnIndex();

                if (index < 0)
                {
                    TLogTool.Info("【TcpServerNet】服务端连接已满");
                    tcpClient.Close();
                }
                else
                {
                    Conn conn = _conns[index];
                    conn.Init(tcpClient.Client, index.ToString());
                    _connectClientCb?.Invoke(conn.GetAddress());
                    TLogTool.Info($"【TcpServerNet】服务器连接上客户端({conn.GetAddress()})");
                    
                    conn.socket.BeginReceive(conn.message.Buffer, conn.message.BuffCount, conn.message.BuffRemain, SocketFlags.None, OnServerReceive, conn);
                    tcpListener.BeginAcceptTcpClient(OnServerConnect, tcpListener);
                }
            }
            catch (Exception e)
            {
                TLogTool.Error("【TcpServerNet】连接客户端时发生错误：" + e);
            }
        }

        //接收消息的回调
        private void OnServerReceive(IAsyncResult ar)
        {
            Conn conn = (Conn) ar.AsyncState;
            if (conn?.socket == null) return;
            int count = conn.socket.EndReceive(ar);

            try
            {
                if (count <= 0)
                {
                    TLogTool.Info($"【TcpServerNet】服务端收到客户端({conn.GetAddress()})的断开连接");
                    DisconnectClient(conn);
                    return;
                }

                conn.message.ParseMessage(count, data => { HandleMsg(conn, _protocol.Decode(data)); });

                conn.socket.BeginReceive(conn.message.Buffer, conn.message.BuffCount, conn.message.BuffRemain, SocketFlags.None, OnServerReceive, conn);
            }
            catch (Exception e)
            {
                TLogTool.Error($"【TcpServerNet】服务端接收信息时发生错误：{e}");
            }
        }

        //处理消息，消息分发
        private void HandleMsg(Conn conn, ProtocolBase protocol)
        {
            if (protocol is ProtocolBytes protocolBytes)
            {
                RequestType requestType = protocolBytes.GetRequest();
                string name = protocolBytes.GetName();
                TLogTool.Info($"【TcpServerNet】服务端接收到客户端({conn.GetAddress()})的请求{protocol.GetRequest()}下的{protocol.GetName()}协议");

                string methodName = "Msg" + name;
                if (_requestDic.TryGetValue(requestType, out var handleMsg))
                {
                    MethodInfo methodInfo = handleMsg.GetType().GetMethod(methodName);
                    if (methodInfo == null)
                    {
                        TLogTool.Error($"{handleMsg.GetType().Name}没有处理{methodName}的方法");
                        return;
                    }

                    Object[] obj = {conn, protocolBytes};
                    methodInfo.Invoke(handleMsg, obj);
                }
            }
        }

        //获取未使用的Conn的下标
        private int GetNewConnIndex()
        {
            if (_conns == null) return -1;

            for (int i = 0; i < _conns.Length; i++)
            {
                if (_conns[i] == null)
                {
                    _conns[i] = new Conn();
                    return i;
                }

                if (_conns[i].isUse == false)
                {
                    return i;
                }
            }

            return -1;
        }

        //线程调用的用于心跳检测的方法
        private void HandleMainTimer(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (_tcpListener != null)
                HeartBeat();

            _timer.Start();
        }

        //心跳检测的判断
        private void HeartBeat()
        {
            TLogTool.Info("【TcpServerNet】心跳检测");
            long timeNow = TinyUtility.Time.GetTimeStamp();

            lock (_conns)
            {
                for (int i = 0; i < _conns.Length; i++)
                {
                    Conn conn = _conns[i];
                    if (conn == null || !conn.isUse) continue;
                    if (conn.lastTickTime < timeNow - _heartBeatTime)
                    {
                        TLogTool.Info($"【TcpServerNet】心跳机制引起服务端断开和客户端({conn.GetAddress()})的连接");
                        DisconnectClient(conn);
                    }
                }
            }
        }

        private void DisconnectClient(Conn conn)
        {
            if (conn == null || conn.isUse == false) return;
            
            _disconnectClientCb?.Invoke(conn.GetAddress());
            conn.Close();
        }

        #endregion
    }
}